// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module gg

import gx
import os
import sokol
import sokol.sapp
import sokol.sgl
import sokol.gfx

pub type FNCb fn(x voidptr)
pub type FNEvent fn(e voidptr, x voidptr)
pub type FNFail fn(msg string, x voidptr)
pub type FNKeyDown fn(c sapp.KeyCode, m sapp.Modifier, x voidptr)
pub type FNChar fn(c u32, x voidptr)

pub struct Config {
pub:
	width         int
	height        int
	use_ortho     bool
	retina        bool
	resizable     bool
	user_data     voidptr
	font_size     int
	create_window bool
	// window_user_ptr voidptr
	window_title  string
	borderless_window bool
	always_on_top bool
	bg_color      gx.Color
	init_fn       FNCb = voidptr(0)
	frame_fn      FNCb = voidptr(0)
	cleanup_fn    FNCb = voidptr(0)
	fail_fn       FNFail = voidptr(0)
	event_fn      FNEvent = voidptr(0)
	keydown_fn    FNKeyDown = voidptr(0) // special case of event_fn
	char_fn       FNChar = voidptr(0) // special case of event_fn
	wait_events   bool // set this to true for UIs, to save power
	fullscreen bool
	scale f32 = 1.0 // vid needs this
	//init_text bool
	font_path string
}

pub struct Context {
	render_text bool
pub mut:
	scale      f32 = 1.0 // will get set to 2.0 for retina, will remain 1.0 for normal
	width      int
	height     int
	clear_pass C.sg_pass_action
	window     C.sapp_desc
	config     Config
	ft &FT
	font_inited bool
}

pub struct Size { pub: width int height int }

fn gg_init_sokol_window(user_data voidptr) {
	mut g := &Context(user_data)
	desc := C.sg_desc{
		mtl_device: sapp.metal_get_device()
		mtl_renderpass_descriptor_cb: sapp.metal_get_renderpass_descriptor
		mtl_drawable_cb: sapp.metal_get_drawable
		d3d11_device: sapp.d3d11_get_device()
		d3d11_device_context: sapp.d3d11_get_device_context()
		d3d11_render_target_view_cb: sapp.d3d11_get_render_target_view
		d3d11_depth_stencil_view_cb: sapp.d3d11_get_depth_stencil_view
	}
	gfx.setup(&desc)
	sgl_desc := C.sgl_desc_t{}
	sgl.setup(&sgl_desc)
	g.scale = sapp.dpi_scale()
	// NB: on older X11, `Xft.dpi` from ~/.Xresources, that sokol uses,
	// may not be set which leads to sapp.dpi_scale reporting incorrectly 0.0
	if g.scale < 0.1 {
		g.scale = 1.0
	}
	//is_high_dpi := sapp.high_dpi()
	//fb_w := sapp.width()
	//fb_h := sapp.height()
	//println('g.scale=$g.scale is_high_dpi=$is_high_dpi fb_w=$fb_w fb_h=$fb_h')
	//if g.config.init_text {
	if g.config.font_path != '' {
		g.ft = new_ft({ font_path: g.config.font_path, scale: sapp.dpi_scale() }) or {panic(err)}
		g.font_inited = true
	}
	if g.config.init_fn != voidptr(0) {
		g.config.init_fn(g.config.user_data)
	}
}

fn gg_frame_fn(user_data voidptr) {
	mut g := &Context(user_data)
	if g.config.frame_fn != voidptr(0) {
		g.config.frame_fn(g.config.user_data)
	}
}

// TODO: remove this hacky workaround...
// NB: todo_remove_this is needed to workaround a v bug,
// where it thinks that &sapp.Event(x) is a function call,
// instead of a cast, if v has not yet seen &sapp.Event used
// as a parameter type.
fn todo_remove_this(e &sapp.Event){}

fn gg_event_fn(ce &C.sapp_event, user_data voidptr){
	e := &sapp.Event(ce)
	mut g := &Context(user_data)
	if g.config.event_fn != voidptr(0) {
		g.config.event_fn(e, g.config.user_data)
	}
	match e.typ {
		.key_down {
			if g.config.keydown_fn != voidptr(0) {
				kdfn := g.config.keydown_fn
				kdfn(e.key_code, e.modifiers, g.config.user_data)
			}
		}
		.char {
			if g.config.char_fn != voidptr(0) {
				cfn := g.config.char_fn
				cfn(e.char_code, g.config.user_data)
			}
		}
		else{}
	}
}

fn gg_cleanup_fn(user_data voidptr){
	mut g := &Context(user_data)
	if g.config.cleanup_fn != voidptr(0) {
		g.config.cleanup_fn(g.config.user_data)
	}
}

fn gg_fail_fn(msg charptr, user_data voidptr){
	mut g := &Context(user_data)
	vmsg := tos3(msg)
	if g.config.fail_fn != voidptr(0) {
		g.config.fail_fn(vmsg, g.config.user_data)
	}else{
		eprintln('gg error: $vmsg')
	}
}

//

pub fn new_context(cfg Config) &Context{
	mut g := &Context{
		width: cfg.width
		height: cfg.height
		clear_pass: gfx.create_clear_pass( f32(cfg.bg_color.r) / 255.0, f32(cfg.bg_color.g) / 255.0,
f32(cfg.bg_color.b) / 255.0, 1.0)
		config: cfg
		render_text: cfg.font_path != ''
		ft: 0
	}

	//C.printf('new_context() %p\n', cfg.user_data)
	window := C.sapp_desc{
		user_data: g
		init_userdata_cb: gg_init_sokol_window
		frame_userdata_cb: gg_frame_fn
		event_userdata_cb: gg_event_fn
		fail_userdata_cb: gg_fail_fn
		cleanup_userdata_cb: gg_cleanup_fn
		window_title: cfg.window_title.str
		html5_canvas_name: cfg.window_title.str
		width: cfg.width
		height: cfg.height
		high_dpi: true
		fullscreen: cfg.fullscreen
	}
	if cfg.use_ortho {}
	else {}
	g.window = window
	return g
}

pub fn (gg &Context) run() {
	sapp.run(&gg.window)
}

pub fn (ctx &Context) draw_rect(x, y, w, h f32, c gx.Color) {
	sgl.c4b(c.r, c.g, c.b, 128)
	sgl.begin_quads()
	sgl.v2f(x * ctx.scale, y * ctx.scale)
	sgl.v2f((x + w) * ctx.scale, y * ctx.scale)
	sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale)
	sgl.v2f(x * ctx.scale, (y + h) * ctx.scale)
	sgl.end()
}

pub fn (ctx &Context) draw_empty_rect(x, y, w, h f32, c gx.Color) {
	sgl.c4b(c.r, c.g, c.b, 128)
	sgl.begin_line_strip()
	if ctx.scale == 1 {
		sgl.v2f(x, y)
		sgl.v2f(x + w, y)
		sgl.v2f(x + w, y + h)
		sgl.v2f(x, y + h)
		sgl.v2f(x, y)
	}
	else {
		sgl.v2f(x * ctx.scale, y * ctx.scale)
		sgl.v2f((x + w) * ctx.scale, y * ctx.scale)
		sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale)
		sgl.v2f(x * ctx.scale, (y + h) * ctx.scale)
		sgl.v2f(x*ctx.scale, y*ctx.scale)
	}
	sgl.end()
}

pub fn create_image(file string) u32 {
	// println('gg create image "$file"')
	if !os.exists(file) {
		println('gg create image no such file "$file"')
		return u32(0)
	}
	// img := stbi.load(file)
	// img.free()
	return 0 // texture
}

pub fn create_image_from_memory(buf byteptr) u32 {
	// texture := gl.gen_texture()
	// img := stbi.load_from_memory(buf)
	// img.free()
	return 0 // texture
}

pub fn (gg &Context) begin() {
	if gg.render_text && gg.font_inited {
		gg.ft.flush()
	}
	sgl.defaults()
	sgl.matrix_mode_projection()
	sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0)
}

pub fn (gg &Context) end() {
	gfx.begin_default_pass(gg.clear_pass, sapp.width(), sapp.height())
	sgl.draw()
	gfx.end_pass()
	gfx.commit()
	if gg.config.wait_events {
		//println('gg: waiting')
		wait_events()
	}
}

pub fn (ctx &Context) draw_line(x, y, x2, y2 f32, c gx.Color) {
	sgl.c4b(c.r, c.g, c.b, 128)
	sgl.begin_line_strip()
	sgl.v2f(x * ctx.scale, y * ctx.scale)
	sgl.v2f(x2 * ctx.scale, y2 * ctx.scale)
	sgl.end()

}
pub fn (ctx &Context) draw_image(x, y, width, height f32, image u32) {
}

pub fn (ctx &Context) draw_rounded_rect(x, y, width, height, radius f32, color gx.Color) {
}

pub fn (ctx &Context) draw_empty_rounded_rect(x, y, width, height, radius f32, border_color gx.Color) {
}

fn C.WaitMessage()

pub fn wait_events() {
	unsafe {
		$if macos  {
		# NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
		# untilDate:[NSDate distantFuture]
		# inMode:NSDefaultRunLoopMode
		# dequeue:YES];
		# [NSApp sendEvent:event];
		}
		$if windows {
			C.WaitMessage()
		}
	}
}
